package com.ning.parking.config;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ning.parking.domain.dto.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * websocket 服务端
 * @param {type} 通道类型
 * @param {drivewayId} 通道ID
 */
@Slf4j
@ServerEndpoint("/ws/ts/{hardwareNo}")
@Component
public class WebSocketServer {


    /**
     * 广播类型
     */
    public static String BROADCAST_MSG_TYPE = "-1";
    public static String MSG_WILDCARD_CHARACTER = "_*";


    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的WebSocketServer对象。
     */
//    private static final ConcurrentHashMap<String, Session> SESSION_POOLS = new ConcurrentHashMap<>();
    /**
     * 机器人心跳30秒 岗亭10秒; 暂定2分钟无心跳过期
     */
    private static final Map<String, Session> SESSION_POOLS = new HashMap<>();


    public Set<String> getOnlineInfo() {
        return SESSION_POOLS.keySet();
    }

    /**
     * 发送消息
     *
     * @param session is客户端地址
     * @param message is 消息体
     * @throws IOException
     */
    public void sendMessage(Session session, String message) throws IOException {
        if (session != null && session.isOpen()) {
            if(ObjectUtil.isNotEmpty(message) && !message.contains(CmdTypeEnum.HEARBEAT_EVENT.toString())){
                log.debug("[sendMessage]:{}:发送数据{}", session.getRequestURI().getPath(),message);
            }
            RemoteEndpoint.Async asyncRemote = session.getAsyncRemote();
            asyncRemote.setSendTimeout(1000);
            asyncRemote.sendText(message);
        } else {
            log.error("session is null or session is close");
        }
    }

    /**
     * 给指定用户发送信息
     *
     * @param userId  is 用户ID
     * @param message is 发送消息体
     */
    public void sendInfo(String userId, String message) {

        if (userId.equals("-1")) {
            for (String s : SESSION_POOLS.keySet()) {
                pushMsg(s, message);
            }
        }else {
            pushMsg(userId, message);
        }


    }

    private void pushMsg(String userId, String message) {
        Session session = SESSION_POOLS.get(userId);
        boolean sessionNull = session == null;
        if (sessionNull || !session.isOpen()) {
            log.error("{} session is {}",userId,  sessionNull  ? "null" :"close");
        }
        try {
            sendMessage(session, message);
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
        }
    }

    /**
     * 广播消息
     *
     * @param message 消息体
     */
    public void broadcastStation(String message) {
        for (String key : SESSION_POOLS.keySet()) {
            try {
                if (key.startsWith("ROBOT_PREFIX")) {
                    continue;
                }
                sendMessage(SESSION_POOLS.get(key), message);
            } catch (Exception e) {
                log.error("broadcast error ." + e.getLocalizedMessage(), e);
            }
        }
    }

    /**
     * 通配符发送
     * @param to _* 通配token;  1234_* 通配通道
     * @param messageStr
     */
    public void sendWildcardCharacterInfo(String to, String messageStr) {
        List<String> tokens = new ArrayList<>();
        for (String key : SESSION_POOLS.keySet()) {

            try {
                if (key.startsWith("ROBOT_PREFIX")) {
                    continue;
                } else if (MSG_WILDCARD_CHARACTER.equals(to)) {
                    String token = getToken(key);
                    if (!tokens.contains(token)) {
                        tokens.add(token);
                        sendMessage(SESSION_POOLS.get(key), messageStr);
                        continue;
                    }
                }

                if (wildcardCharacter(to,key)){
                    sendMessage(SESSION_POOLS.get(key), messageStr);
                }

            } catch (IOException e) {
                log.error("sendWildcardCharacterInfo error ." + e.getLocalizedMessage(), e);
            }
        }
    }

    private String getToken(String key) {
        try {
            String[] split = key.split("_");
            return split[split.length-1];
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 原字符串
     * @param wildcardStr  123_* *可以全部跳过
     * @param to 完整key
     * @return
     */
    private boolean wildcardCharacter(String wildcardStr, String to) {
        if (ObjectUtil.isEmpty(wildcardStr) || ObjectUtil.isEmpty(to)) {
            return false;
        }

        String[] wildcards = wildcardStr.split("_");
        String[] key = to.split("_");

        if (wildcards.length == 0 || key.length == 0 ||  key.length != wildcards.length){
            return false;
        }

        for (int i = 0; i < wildcards.length; i++) {
            String wildcard = wildcards[i];
            if (wildcard.equals("*")) {
                continue;
            }
            String k1 = key[i];
            if (!k1.equals(wildcard)) {
                return false;
            }
        }
        return true;
    }

    /**
     * to = -1 广播消息
     * 收到客户端信息后，根据接收人的userID把消息推下去或者群发
     *
     * @param message is 消息体
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        Message message1 = new Message();
        message1.setCmdType(CmdTypeEnum.HEARBEAT_EVENT);
        Message msg = JSONUtil.toBean(JSONUtil.parseObj(message1), Message.class);
        msg.setSession(session);
        if(ObjectUtil.isNotEmpty(message) && !message.contains(CmdTypeEnum.HEARBEAT_EVENT.toString())){
            log.info("server get={}", message);
        }else {
            if (ObjectUtil.isNotEmpty(message)) {
                String from = msg.getFrom();
                if (from == null || "".equals(from)) {
                    String path = session.getRequestURI().getPath();
                    String[] split = path.split("/");
                    if (split != null && split.length > 0) {
                        from = split[split.length - 1];
                    }
                }
                SESSION_POOLS.put(from,session);
            }
        }
        //SpringUtil.getBean(SocketHandlerFactory.class).getHandler(msg.getCmdType()).processor(msg);
    }

    /**
     * 建立连接成功调用
     *
     * @param session
     * @param hardwareNo
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "hardwareNo") String hardwareNo) {
        SESSION_POOLS.put(hardwareNo, session);
        log.info("{}", session.getRequestURI());
        log.info("加入:{},当前总链接:{}", hardwareNo, SESSION_POOLS.size());
    }

    /**
     * 关闭连接时调用
     *
     * @param hardwareNo
     */
    @OnClose
    public void onClose(Session session, @PathParam(value = "hardwareNo") String hardwareNo) {
        for (Session s : SESSION_POOLS.values()) {
            if (Objects.equals(s.getId(), session.getId())){
                try {
                    log.info(hardwareNo+" onClose method close");
                    session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, hardwareNo+" onClose method close"));
                } catch (IOException e) {
                    log.error(hardwareNo+" onClose method close error");
                    log.error(e.getLocalizedMessage(), e);
                }
            }
        }
        SESSION_POOLS.remove(hardwareNo);
        log.info("退出岗亭管理:{}当前总链接:{}", hardwareNo, SESSION_POOLS.size());
    }

    /**
     * 错误时调用
     *
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("{} 发生错误 {}", session.getRequestURI(), throwable);
    }
}
